//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.basiclab.iot.core.time;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class WheelTimer<E> {
    private long tickDuration;
    private int ticksPerWheel;
    private volatile int currentTickIndex = 0;
    private CopyOnWriteArrayList<ExpirationListener<E>> expirationListeners = new CopyOnWriteArrayList();
    private ArrayList<TimeSlot<E>> wheel;
    private Map<E, TimeSlot<E>> indicator = new ConcurrentHashMap();
    private AtomicBoolean shutdown = new AtomicBoolean(false);
    private ReadWriteLock lock = new ReentrantReadWriteLock();
    private Thread workThread;

    public WheelTimer(int tickDuration, TimeUnit timeUnit, int ticksPerWheel) {
        if (timeUnit == null) {
            throw new NullPointerException("time unit");
        } else if (tickDuration <= 0) {
            throw new IllegalArgumentException("tickDuration must be greater than 0" + tickDuration);
        } else {
            this.wheel = new ArrayList();
            this.tickDuration = TimeUnit.MILLISECONDS.convert((long)tickDuration, timeUnit);
            this.ticksPerWheel = ticksPerWheel;

            for(int i = 0; i < this.ticksPerWheel; ++i) {
                this.wheel.add(new TimeSlot(i));
            }

            this.wheel.trimToSize();
            this.workThread = new Thread(new WheelTimer.TickWorker(), "wheel-timer");
        }
    }

    public void start() {
        if (this.shutdown.get()) {
            throw new IllegalStateException("the thread is stoped");
        } else {
            if (!this.workThread.isAlive()) {
                this.workThread.start();
            }

        }
    }

    public boolean stop() {
        if (!this.shutdown.compareAndSet(false, true)) {
            return false;
        } else {
            boolean interrupted = false;

            while(this.workThread.isAlive()) {
                this.workThread.interrupt();

                try {
                    this.workThread.join(1000L);
                } catch (Exception var3) {
                    interrupted = true;
                }
            }

            if (interrupted) {
                Thread.currentThread().interrupt();
            }

            return true;
        }
    }

    public void addExpirationListener(ExpirationListener<E> listener) {
        this.expirationListeners.add(listener);
    }

    public void removeExpirationListner(ExpirationListener<E> listener) {
        this.expirationListeners.remove(listener);
    }

    public long add(E e) {
        synchronized(e) {
            this.checkAdd(e);
            int previousTickIndex = this.getPreviousTickIndex();
            TimeSlot<E> timeSlotSet = (TimeSlot)this.wheel.get(previousTickIndex);
            timeSlotSet.add(e);
            this.indicator.put(e, timeSlotSet);
            return (long)(this.ticksPerWheel - 1) * this.tickDuration;
        }
    }

    public boolean remove(E e) {
        synchronized(e) {
            TimeSlot<E> timeSlot = (TimeSlot)this.indicator.get(e);
            if (timeSlot == null) {
                return false;
            } else {
                this.indicator.remove(e);
                return timeSlot.remove(e);
            }
        }
    }

    public void notifyExpired(int idx) {
        TimeSlot<E> timeSlot = (TimeSlot)this.wheel.get(idx);
        Set<E> elements = timeSlot.getElements();
        Iterator var4 = elements.iterator();

        while(var4.hasNext()) {
            E e = (E) var4.next();
            timeSlot.remove(e);
            synchronized(e) {
                TimeSlot<E> latestSLot = (TimeSlot)this.indicator.get(e);
                if (latestSLot.equals(timeSlot)) {
                    this.indicator.remove(e);
                }
            }

            Iterator var6 = this.expirationListeners.iterator();

            while(var6.hasNext()) {
                ExpirationListener<E> listener = (ExpirationListener)var6.next();
                listener.expired(e);
            }
        }

    }

    private int getPreviousTickIndex() {
        this.lock.readLock().lock();

        int var2;
        try {
            int cti = this.currentTickIndex;
            if (cti != 0) {
                var2 = cti - 1;
                return var2;
            }

            var2 = this.ticksPerWheel - 1;
        } catch (Exception var6) {
            return this.currentTickIndex - 1;
        } finally {
            this.lock.readLock().unlock();
        }

        return var2;
    }

    private void checkAdd(E e) {
        TimeSlot<E> slot = (TimeSlot)this.indicator.get(e);
        if (slot != null) {
            slot.remove(e);
        }

    }

    private class TickWorker implements Runnable {
        private long startTime;
        private long tick;

        private TickWorker() {
            this.tick = 1L;
        }

        @Override
        public void run() {
            this.startTime = System.currentTimeMillis();
            this.tick = 1L;

            for(int i = 0; !WheelTimer.this.shutdown.get(); ++i) {
                if (i == WheelTimer.this.wheel.size()) {
                    i = 0;
                }

                WheelTimer.this.lock.writeLock().lock();

                try {
                    WheelTimer.this.currentTickIndex = i;
                } catch (Exception var6) {
                } finally {
                    WheelTimer.this.lock.writeLock().unlock();
                }

                WheelTimer.this.notifyExpired(WheelTimer.this.currentTickIndex);
                this.waitForNexTick();
            }

        }

        private void waitForNexTick() {
            long currentTime = System.currentTimeMillis();
            long sleepTime = WheelTimer.this.tickDuration * this.tick - (currentTime - this.startTime);
            if (sleepTime > 0L) {
                try {
                    Thread.sleep(sleepTime);
                } catch (Exception var9) {
                } finally {
                    return;
                }
            }

        }
    }
}
